import {mapshaper} from './api';
import {GUI}        from "./gui";
import {runCommand} from "./command";


let index = 1;

const cache = {};
const POINT = "Point";
const MULTI_POLYGON = "MultiPolygon";
const POLYGON = "Polygon";
const MULTI_LINE_STRING = "MultiLineString";

const COMBINE_JSON = "CombineJSON";

const MAX_LNG_LAT_COUNT = 10000;
const MIN_LNG_LAT_COUNT = 500;

// mapshaper的格式化geojson是异步的, 并且只有一个replaceWriteFiles的接口可以自定义接受处理结果的回调.
// 这就导致如果同时多次触发格式化, replaceWriteFiles被多次调用的话, 前面的回调函数就会被覆盖从而不执行.
// 这边统一replaceWriteFiles, 回调会根据文件名把格式化后的GeoJSON缓存起来,
// 然后在每次格式化之前, 随机一个文件名并记录, 这样根据文件名即可拿回指定的GeoJSON.
mapshaper.internal.replaceWriteFiles(function(outputFiles, opts, done) {
    cache[opts.file] = JSON.parse(outputFiles[0].content.toString());
    done();
});

function getData(name) {
    const res = cache[name];
    delete cache[name];

    return res;
}

function simplify(content, opts) {
    return new Promise((resolve, reject) => {
        const gui = new GUI("geo.json", content);

        const name = uuname();

        runCommand(gui, "-simplify percentage="+ opts.simplifyPct +` -o ${name}`, function () {
            resolve(getData(name));
        });
    });
}

function dissolve(content, opts) {
    return new Promise(resolve => {

        const gui = new GUI("geo.json", content);

        const name = uuname();

        runCommand(gui, "-simplify percentage="+opts.simplifyPct +` -dissolve -o ${name}`, function (error) {

            if (error) {
                console.log(error);
            }
            const output = getData(name);
            const [geometry] = output.geometries;
            resolve({
                type: "FeatureCollection",
                features: [
                    {
                        type: "Feature",
                        geometry: geometry,
                        properties: {
                        }
                    }
                ]
            });
        });

    })
}

export function processGeoJSON(data, opts, callback) {

    // 过滤掉点类型.
    data.features = data.features.filter(feature => feature.geometry && feature.geometry.type !== POINT);

    // 过滤出Geometry: MultiLineString. (十段线)
    // 格式化之后再放回去.
    const multiLines = data.features.filter(feature => feature.geometry && feature.geometry.type === MULTI_LINE_STRING );

    const content = JSON.stringify(correctMultiPolygon(data));

    opts = opts || {};
    opts.simplifyPct = opts.simplifyPct == null ? getSimplyPct(data.features) : opts.simplifyPct;

    console.log(opts);

    // 简化 & 合并.
    const sim = simplify(content, opts);
    const dis = dissolve(content, opts);

    Promise.all([sim, dis])
        .then(([simplified, combine]) => {

            simplified.features.push(...multiLines);

            // 补充name&bbox属性.
            const [feature] = combine.features;
            feature.properties.name = opts.name || COMBINE_JSON;
            feature.properties.bbox = getBBox(feature.geometry, simplified.features);

            callback({
                all: simplified,
                combine
            });
        });

}

function getSimplyPct(features) {

    const lngLatCount = getLngLatCount(features);

    const log2 = Math.log2;

    if (lngLatCount > MAX_LNG_LAT_COUNT) return 0.5;

    if (lngLatCount < MIN_LNG_LAT_COUNT) return 1;

    const min = log2(MIN_LNG_LAT_COUNT);
    const max = log2(MAX_LNG_LAT_COUNT);

    return 1 - (log2(lngLatCount) - min) / (max - min) * 0.5;
}

function getLngLatCount(features) {

    let sum = 0;

    const addPolygon = coords => {
        coords.forEach(coord => sum += coord.length);
    }

    features.forEach(feature => {
        const {coordinates, type} = feature.geometry;

        switch (type) {
            case POLYGON:
                addPolygon(coordinates);
                return;
            case MULTI_POLYGON:
                coordinates.forEach(addPolygon);
                return;
        }

    });

    return sum;
}


function getBBox(geometry, features) {


    let west = Number.MAX_VALUE,
        east = -west,
        north = -west,
        south = west;


    features = features.filter(feature => {
        const {properties, geometry} = feature;
        if (!properties || !geometry) return false;

        return geometry.type === MULTI_POLYGON || geometry.type === POLYGON
    });

    if (features.length === 0) {
        return getMultiPolygonBBox(geometry);
    }

    features.forEach(feature => {
        const [w, s, e, n] = feature.properties.bbox || getMultiPolygonBBox(feature.geometry);
        west = Math.min(west, w);
        east = Math.max(east, e);
        north = Math.max(north, n);
        south = Math.min(south, s);
    });

    return [west, south, east, north];

}

// 计算MultiPolygon的BBox
function getMultiPolygonBBox({coordinates, type}) {
    if (type === POLYGON) {
        coordinates = [coordinates];
    }

    let west = Number.MAX_VALUE,
        east = -west,
        north = -west,
        south = west;

    coordinates.forEach(polygon => polygon.forEach(shape => shape.forEach(([lng, lat]) => {
        west = Math.min(west, lng);
        east = Math.max(east, lng);
        north = Math.max(north, lat);
        south = Math.min(south, lat);
    })));

    return [west, south, east, north];
}

// unique geojson name.
function uuname() {
    return `${index++}.json`;
}


/**
 * 修正 MultiPolygon.
 * 对于从民政局获取的GeoJSON, 可能会存在MultiPolygon结构为
 * [[
 *     [lngLats],
 *     [lngLats]
 * ]]
 * 应将第一层拆解, 转为
 * [
 *     [[lngLats]],
 *     [[lngLats]]
 * ]
 * @param geoJson
 * @returns {*}
 */
function correctMultiPolygon(geoJson) {
    geoJson.features = geoJson.features.map(feature => {

        const {geometry} = feature;

        const {type, coordinates} = geometry;

        if (type !== MULTI_POLYGON || coordinates.length !== 1) return feature;

        geometry.coordinates = geometry.coordinates[0].map(polygon => [polygon]);

        return feature;
    });

    return geoJson;
}